<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>双Token无感刷新方案 | 技术小馆</title>
    <link href="https://cdn.staticfile.org/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <link href="https://cdn.staticfile.org/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/mermaid@latest/dist/mermaid.min.js"></script>
    <style>
        body {
            font-family: 'Noto Sans SC', Tahoma, Arial, Roboto, "Droid Sans", "Helvetica Neue", "Droid Sans Fallback", "Heiti SC", "Hiragino Sans GB", Simsun, sans-serif;
            background-color: #f8fafc;
            color: #334155;
        }
        .hero {
            background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
        }
        .card {
            transition: transform 0.3s ease, box-shadow 0.3s ease;
        }
        .card:hover {
            transform: translateY(-5px);
            box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
        }
        .highlight {
            position: relative;
            z-index: 1;
        }
        .highlight:after {
            content: '';
            position: absolute;
            bottom: 0;
            left: 0;
            width: 100%;
            height: 30%;
            background-color: #a78bfa;
            opacity: 0.3;
            z-index: -1;
            transition: height 0.3s ease;
        }
        .highlight:hover:after {
            height: 60%;
        }
        .code-block {
            background-color: #1e293b;
            border-radius: 0.5rem;
            position: relative;
        }
        .code-block pre {
            margin: 0;
            padding: 1.5rem;
            overflow-x: auto;
        }
        .code-block:before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 2.5rem;
            background-color: #334155;
            border-top-left-radius: 0.5rem;
            border-top-right-radius: 0.5rem;
        }
        .code-block:after {
            content: '';
            position: absolute;
            top: 0.75rem;
            left: 1rem;
            width: 0.75rem;
            height: 0.75rem;
            border-radius: 50%;
            background-color: #ef4444;
            box-shadow: 1.25rem 0 0 #fbbf24, 2.5rem 0 0 #22c55e;
        }
        .table-container {
            overflow-x: auto;
        }
        table {
            min-width: 100%;
            border-collapse: separate;
            border-spacing: 0;
        }
        table th {
            background-color: #4f46e5;
            color: white;
            position: sticky;
            top: 0;
        }
        table th, table td {
            padding: 0.75rem 1rem;
            text-align: left;
            border-bottom: 1px solid #e2e8f0;
        }
        table tr:hover td {
            background-color: #f1f5f9;
        }
        .footer-link {
            transition: color 0.3s ease;
        }
        .footer-link:hover {
            color: #a78bfa;
        }
        .mermaid {
            background-color: white;
            border-radius: 0.5rem;
            padding: 1.5rem;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
        }
    </style>
</head>
<body class="antialiased">
    <!-- Hero Section -->
    <section class="hero text-white py-20 px-4 md:py-32">
        <div class="container mx-auto max-w-6xl">
            <div class="flex flex-col md:flex-row items-center">
                <div class="md:w-1/2 mb-10 md:mb-0">
                    <h1 class="text-4xl md:text-5xl font-bold mb-6 leading-tight font-serif">双Token无感刷新方案</h1>
                    <p class="text-xl md:text-2xl mb-8 opacity-90 leading-relaxed">让用户刷一天App都不会感知到Token刷新，解决登录突然失效的糟糕体验</p>
                    <div class="flex flex-wrap gap-4">
                        <div class="flex items-center bg-white bg-opacity-10 px-4 py-2 rounded-full">
                            <i class="fas fa-shield-alt mr-2"></i>
                            <span>黑客截获也无效</span>
                        </div>
                        <div class="flex items-center bg-white bg-opacity-10 px-4 py-2 rounded-full">
                            <i class="fas fa-tachometer-alt mr-2"></i>
                            <span>百万并发稳如老狗</span>
                        </div>
                        <div class="flex items-center bg-white bg-opacity-10 px-4 py-2 rounded-full">
                            <i class="fas fa-lightbulb mr-2"></i>
                            <span>Redis原子操作+设备指纹</span>
                        </div>
                    </div>
                </div>
                <div class="md:w-1/2 flex justify-center">
                    <div class="bg-white bg-opacity-10 p-2 rounded-xl border border-white border-opacity-20">
                        <img src="https://cdn.nlark.com/yuque/0/2025/png/21449790/1743390236489-61976211-8c08-4d21-b803-13297d8f89f9.png" alt="双Token方案示意图" class="rounded-lg shadow-xl">
                    </div>
                </div>
            </div>
        </div>
    </section>

    <!-- Main Content -->
    <div class="container mx-auto max-w-6xl px-4 py-12">
        <!-- Problem Section -->
        <section class="mb-20">
            <div class="flex items-center mb-8">
                <h2 class="text-3xl font-bold font-serif relative">为什么你的登录方案总被骂？</h2>
                <div class="ml-4 text-red-500 text-2xl">
                    <i class="fas fa-exclamation-triangle"></i>
                </div>
            </div>
            
            <div class="grid md:grid-cols-2 gap-8 mb-12">
                <div>
                    <p class="text-lg mb-6 leading-relaxed">去年我们系统上线新版本后，用户集体炸锅——刷着刷着突然跳回登录页，购物车里的限量球鞋全没了！原来单Token方案就像一张<strong>纸质门票</strong>，过期就得重新排队买票。</p>
                    <p class="text-lg mb-6 leading-relaxed">而双Token方案是<strong>门票+电子手环</strong>组合：</p>
                    <ul class="space-y-3 mb-8">
                        <li class="flex items-start">
                            <i class="fas fa-ticket-alt text-purple-500 mt-1 mr-3"></i>
                            <span><strong>Access Token（门票）</strong>：短时效（2小时），用于接口访问</span>
                        </li>
                        <li class="flex items-start">
                            <i class="fas fa-barcode text-purple-500 mt-1 mr-3"></i>
                            <span><strong>Refresh Token（电子手环）</strong>：长时效（7天），用于续期门票</span>
                        </li>
                    </ul>
                    <p class="text-lg leading-relaxed">但你以为这就完了？我们第一次实现时，前端小哥在厕所堵我："每次刷新都要loading转圈，用户以为卡死了！" 这才明白要做<strong>无感刷新</strong>——就像迪士尼的快速通行证，玩完一个项目自动预约下一个。</p>
                </div>
                <div>
                    <div class="code-block">
                        <pre class="text-gray-300"><code class="language-json">// 典型登录响应
{
  "access_token": "eyJhbGciOiJIUz...",
  "expires_in": 7200,
  "refresh_token": "dGhpcyBpcyBh...",
  "refresh_expires": 604800
}</code></pre>
                    </div>
                </div>
            </div>
        </section>

        <!-- Pitfalls Section -->
        <section class="mb-20">
            <h2 class="text-3xl font-bold font-serif mb-8">这些坑你躲得过吗？</h2>
            
            <div class="grid md:grid-cols-3 gap-6 mb-12">
                <!-- Pitfall 1 -->
                <div class="card bg-white p-6 rounded-xl shadow-md border border-gray-100">
                    <div class="text-purple-500 text-3xl mb-4">
                        <i class="fas fa-bolt"></i>
                    </div>
                    <h3 class="text-xl font-bold mb-3">连环刷新风暴</h3>
                    <p class="text-gray-600 mb-4">前端同时发起多个请求，每个请求发现token过期都去刷新，瞬间把服务端打爆。</p>
                    <div class="code-block text-sm">
                        <pre class="text-gray-300"><code class="language-java">// Redis加锁伪代码
String key = "refresh_lock:" + userId;
Boolean locked = redis.setIfAbsent(
    key, "1", 10, TimeUnit.SECONDS
);
if (!locked) {
    throw new BusinessException("刷新操作进行中");
}</code></pre>
                    </div>
                </div>
                
                <!-- Pitfall 2 -->
                <div class="card bg-white p-6 rounded-xl shadow-md border border-gray-100">
                    <div class="text-purple-500 text-3xl mb-4">
                        <i class="fas fa-user-secret"></i>
                    </div>
                    <h3 class="text-xl font-bold mb-3">Token劫持漏洞</h3>
                    <p class="text-gray-600 mb-4">Refresh Token被盗后，黑客可以无限生成新Token。</p>
                    <div class="code-block text-sm">
                        <pre class="text-gray-300"><code class="language-java">String deviceFingerprint = request.getHeader("X-Device-ID");
String refreshToken = jwtTool.generateRefreshToken(
    userId, deviceFingerprint
);</code></pre>
                    </div>
                </div>
                
                <!-- Pitfall 3 -->
                <div class="card bg-white p-6 rounded-xl shadow-md border border-gray-100">
                    <div class="text-purple-500 text-3xl mb-4">
                        <i class="fas fa-sync-alt"></i>
                    </div>
                    <h3 class="text-xl font-bold mb-3">多端登录互踢</h3>
                    <p class="text-gray-600 mb-4">用户在手机端刷新Token后，PC端突然下线。</p>
                    <div class="code-block text-sm">
                        <pre class="text-gray-300"><code class="language-java">// 采用Token白名单机制
List&lt;String&gt; validTokens = 
    tokenStore.getValidTokens(userId);
if (!validTokens.contains(refreshToken)) {
    throw new InvalidTokenException();
}</code></pre>
                    </div>
                </div>
            </div>
        </section>

        <!-- Perfect Solution Section -->
        <section class="mb-20">
            <div class="flex items-center mb-8">
                <h2 class="text-3xl font-bold font-serif">完美方案设计</h2>
                <div class="ml-4 text-purple-500 text-2xl">
                    <i class="fas fa-star"></i>
                </div>
            </div>
            
            <div class="mb-12">
                <div class="mermaid">
                    graph TD
                        A[用户请求] -->|携带Access Token| B{Token有效?}
                        B -->|是| C[处理请求]
                        B -->|否| D[检查Refresh Token]
                        D -->|有效| E[生成新Access Token]
                        D -->|无效| F[返回401]
                        E --> G[更新响应头New-Token]
                        G --> C
                </div>
            </div>
            
            <h3 class="text-2xl font-bold font-serif mb-6">六大核心机制</h3>
            <div class="grid md:grid-cols-2 lg:grid-cols-3 gap-4 mb-8">
                <div class="bg-white p-4 rounded-lg shadow-sm border border-gray-100">
                    <div class="flex items-center">
                        <div class="bg-purple-100 text-purple-600 p-2 rounded-full mr-3">
                            <i class="fas fa-sliders-h"></i>
                        </div>
                        <h4 class="font-bold">滑动过期</h4>
                    </div>
                    <p class="text-gray-600 text-sm mt-2">每次刷新重置Refresh Token有效期</p>
                </div>
                <div class="bg-white p-4 rounded-lg shadow-sm border border-gray-100">
                    <div class="flex items-center">
                        <div class="bg-purple-100 text-purple-600 p-2 rounded-full mr-3">
                            <i class="fas fa-fire"></i>
                        </div>
                        <h4 class="font-bold">单次有效</h4>
                    </div>
                    <p class="text-gray-600 text-sm mt-2">旧Refresh Token用完即焚</p>
                </div>
                <div class="bg-white p-4 rounded-lg shadow-sm border border-gray-100">
                    <div class="flex items-center">
                        <div class="bg-purple-100 text-purple-600 p-2 rounded-full mr-3">
                            <i class="fas fa-hashtag"></i>
                        </div>
                        <h4 class="font-bold">使用次数限制</h4>
                    </div>
                    <p class="text-gray-600 text-sm mt-2">单个Refresh Token最多刷新5次</p>
                </div>
                <div class="bg-white p-4 rounded-lg shadow-sm border border-gray-100">
                    <div class="flex items-center">
                        <div class="bg-purple-100 text-purple-600 p-2 rounded-full mr-3">
                            <i class="fas fa-network-wired"></i>
                        </div>
                        <h4 class="font-bold">IP绑定</h4>
                    </div>
                    <p class="text-gray-600 text-sm mt-2">检测到IP突变立即失效</p>
                </div>
                <div class="bg-white p-4 rounded-lg shadow-sm border border-gray-100">
                    <div class="flex items-center">
                        <div class="bg-purple-100 text-purple-600 p-2 rounded-full mr-3">
                            <i class="fas fa-paint-brush"></i>
                        </div>
                        <h4 class="font-bold">流量染色</h4>
                    </div>
                    <p class="text-gray-600 text-sm mt-2">区分正常请求和刷新请求</p>
                </div>
                <div class="bg-white p-4 rounded-lg shadow-sm border border-gray-100">
                    <div class="flex items-center">
                        <div class="bg-purple-100 text-purple-600 p-2 rounded-full mr-3">
                            <i class="fas fa-umbrella"></i>
                        </div>
                        <h4 class="font-bold">熔断降级</h4>
                    </div>
                    <p class="text-gray-600 text-sm mt-2">刷新服务异常时降级到重新登录</p>
                </div>
            </div>
        </section>

        <!-- Implementation Section -->
        <section class="mb-20">
            <h2 class="text-3xl font-bold font-serif mb-8">Spring Security整合实战</h2>
            
            <div class="grid md:grid-cols-2 gap-8 mb-12">
                <div>
                    <h3 class="text-xl font-bold mb-4 flex items-center">
                        <i class="fas fa-code text-purple-500 mr-2"></i>
                        <span>1. 自定义JWT工具类</span>
                    </h3>
                    <div class="code-block">
                        <pre class="text-gray-300"><code class="language-java">public class JwtKit {
    // 生成双Token
    public static TokenPair generatePair(String userId, String deviceId) {
        String accessToken = Jwts.builder()
                .setSubject(userId)
                .setExpiration(new Date(
                    System.currentTimeMillis() + 7200_000))
                .signWith(SECRET_KEY)
                .compact();
        
        String refreshToken = Jwts.builder()
                .setSubject(userId)
                .claim("device", deviceId)
                .setExpiration(new Date(
                    System.currentTimeMillis() + 604800_000))
                .signWith(SECRET_KEY)
                .compact();
        
        return new TokenPair(accessToken, refreshToken);
    }
}</code></pre>
                    </div>
                </div>
                <div>
                    <h3 class="text-xl font-bold mb-4 flex items-center">
                        <i class="fas fa-exchange-alt text-purple-500 mr-2"></i>
                        <span>2. 刷新端点实现</span>
                    </h3>
                    <div class="code-block">
                        <pre class="text-gray-300"><code class="language-java">@PostMapping("/refresh")
public ResponseEntity&lt;?&gt; refresh(
    @RequestHeader("Authorization") String refreshToken,
    @RequestHeader("X-Device-ID") String deviceId) {
    
    Claims claims = JwtKit.parseRefreshToken(refreshToken);
    if (!deviceId.equals(claims.get("device"))) {
        throw new InvalidTokenException("设备不匹配");
    }
    
    String newAccessToken = JwtKit.generateAccessToken(
        claims.getSubject());
    String newRefreshToken = JwtKit.rotateRefreshToken(
        refreshToken);
    
    return ResponseEntity.ok(
        new TokenPair(newAccessToken, newRefreshToken));
}</code></pre>
                    </div>
                </div>
            </div>
            
            <h3 class="text-xl font-bold mb-4 flex items-center">
                <i class="fas fa-project-diagram text-purple-500 mr-2"></i>
                <span>3. 网关过滤器（Spring Cloud Gateway）</span>
            </h3>
            <div class="code-block mb-12">
                <pre class="text-gray-300"><code class="language-java">public class TokenRefreshFilter implements GlobalFilter {
    @Override
    public Mono&lt;Void&gt; filter(ServerWebExchange exchange, 
            GatewayFilterChain chain) {
        // 1. 检查Access Token是否过期
        // 2. 若过期，调用Auth服务刷新
        // 3. 将新Token写入响应头
        // 4. 重试原始请求
    }
}</code></pre>
            </div>
        </section>

        <!-- Performance Section -->
        <section class="mb-20">
            <h2 class="text-3xl font-bold font-serif mb-8">性能优化</h2>
            
            <h3 class="text-xl font-bold mb-4">压测数据对比</h3>
            <div class="table-container mb-8">
                <table class="w-full">
                    <thead>
                        <tr>
                            <th>方案</th>
                            <th>QPS</th>
                            <th>平均延时</th>
                            <th>CPU使用率</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>单Token</td>
                            <td>12k</td>
                            <td>85ms</td>
                            <td>78%</td>
                        </tr>
                        <tr>
                            <td>双Token基础版</td>
                            <td>9k</td>
                            <td>120ms</td>
                            <td>92%</td>
                        </tr>
                        <tr>
                            <td>双Token优化版</td>
                            <td>28k</td>
                            <td>45ms</td>
                            <td>65%</td>
                        </tr>
                    </tbody>
                </table>
            </div>
            
            <h3 class="text-xl font-bold mb-4">五大优化技巧</h3>
            <div class="grid md:grid-cols-2 lg:grid-cols-5 gap-4 mb-8">
                <div class="bg-white p-4 rounded-lg shadow-sm border border-gray-100 text-center">
                    <div class="text-purple-500 text-2xl mb-2">
                        <i class="fas fa-clock"></i>
                    </div>
                    <h4 class="font-bold mb-1">异步刷新</h4>
                    <p class="text-gray-600 text-sm">使用CompletableFuture并行处理</p>
                </div>
                <div class="bg-white p-4 rounded-lg shadow-sm border border-gray-100 text-center">
                    <div class="text-purple-500 text-2xl mb-2">
                        <i class="fas fa-database"></i>
                    </div>
                    <h4 class="font-bold mb-1">本地缓存</h4>
                    <p class="text-gray-600 text-sm">在网关层缓存5分钟内的新Token</p>
                </div>
                <div class="bg-white p-4 rounded-lg shadow-sm border border-gray-100 text-center">
                    <div class="text-purple-500 text-2xl mb-2">
                        <i class="fas fa-layer-group"></i>
                    </div>
                    <h4 class="font-bold mb-1">批处理</h4>
                    <p class="text-gray-600 text-sm">合并多个刷新请求</p>
                </div>
                <div class="bg-white p-4 rounded-lg shadow-sm border border-gray-100 text-center">
                    <div class="text-purple-500 text-2xl mb-2">
                        <i class="fas fa-plug"></i>
                    </div>
                    <h4 class="font-bold mb-1">连接池优化</h4>
                    <p class="text-gray-600 text-sm">调整HTTP客户端参数</p>
                </div>
                <div class="bg-white p-4 rounded-lg shadow-sm border border-gray-100 text-center">
                    <div class="text-purple-500 text-2xl mb-2">
                        <i class="fas fa-tint"></i>
                    </div>
                    <h4 class="font-bold mb-1">令牌桶限流</h4>
                    <p class="text-gray-600 text-sm">防止刷新接口被刷爆</p>
                </div>
            </div>
            
            <div class="code-block mb-12">
                <pre class="text-gray-300"><code class="language-java">// 异步刷新示例
public Mono&lt;Void&gt; asyncRefresh(ServerWebExchange exchange) {
    return Mono.fromFuture(CompletableFuture.supplyAsync(() -> {
        return authService.refreshToken(oldRefreshToken);
    })).flatMap(newToken -> {
        exchange.getResponse().getHeaders()
            .set("X-New-Token", newToken);
        return chain.filter(exchange);
    });
}</code></pre>
            </div>
        </section>

        <!-- Security Section -->
        <section class="mb-20">
            <h2 class="text-3xl font-bold font-serif mb-8">你的认证方案能抗住这五招吗？</h2>
            
            <h3 class="text-xl font-bold mb-4">安全攻防演练</h3>
            <div class="grid md:grid-cols-2 lg:grid-cols-5 gap-4 mb-8">
                <div class="bg-red-50 p-4 rounded-lg border border-red-100">
                    <div class="text-red-500 text-2xl mb-2">
                        <i class="fas fa-recycle"></i>
                    </div>
                    <h4 class="font-bold mb-1 text-red-700">重放攻击</h4>
                    <p class="text-red-600 text-sm">在Token过期前截获并重复使用</p>
                </div>
                <div class="bg-red-50 p-4 rounded-lg border border-red-100">
                    <div class="text-red-500 text-2xl mb-2">
                        <i class="fas fa-user-secret"></i>
                    </div>
                    <h4 class="font-bold mb-1 text-red-700">中间人攻击</h4>
                    <p class="text-red-600 text-sm">伪造CA证书拦截HTTPS请求</p>
                </div>
                <div class="bg-red-50 p-4 rounded-lg border border-red-100">
                    <div class="text-red-500 text-2xl mb-2">
                        <i class="fas fa-rainbow"></i>
                    </div>
                    <h4 class="font-bold mb-1 text-red-700">彩虹表爆破</h4>
                    <p class="text-red-600 text-sm">JWT密钥强度不足</p>
                </div>
                <div class="bg-red-50 p-4 rounded-lg border border-red-100">
                    <div class="text-red-500 text-2xl mb-2">
                        <i class="fas fa-stopwatch"></i>
                    </div>
                    <h4 class="font-bold mb-1 text-red-700">时序攻击</h4>
                    <p class="text-red-600 text-sm">通过响应时间差异推断用户是否存在</p>
                </div>
                <div class="bg-red-50 p-4 rounded-lg border border-red-100">
                    <div class="text-red-500 text-2xl mb-2">
                        <i class="fas fa-bomb"></i>
                    </div>
                    <h4 class="font-bold mb-1 text-red-700">分布式拒绝刷新</h4>
                    <p class="text-red-600 text-sm">同时发起百万级刷新请求</p>
                </div>
            </div>
            
            <h3 class="text-xl font-bold mb-4">终极防御方案</h3>
            <ul class="grid md:grid-cols-2 gap-4 mb-12">
                <li class="flex items-start bg-white p-4 rounded-lg shadow-sm border border-gray-100">
                    <i class="fas fa-key text-purple-500 mt-1 mr-3"></i>
                    <div>
                        <h4 class="font-bold">动态密钥轮转</h4>
                        <p class="text-gray-600 text-sm">每小时更换JWT签名密钥</p>
                    </div>
                </li>
                <li class="flex items-start bg-white p-4 rounded-lg shadow-sm border border-gray-100">
                    <i class="fas fa-lock text-purple-500 mt-1 mr-3"></i>
                    <div>
                        <h4 class="font-bold">双向TLS认证</h4>
                        <p class="text-gray-600 text-sm">服务端和客户端交换证书</p>
                    </div>
                </li>
                <li class="flex items-start bg-white p-4 rounded-lg shadow-sm border border-gray-100">
                    <i class="fas fa-microchip text-purple-500 mt-1 mr-3"></i>
                    <div>
                        <h4 class="font-bold">硬件安全模块</h4>
                        <p class="text-gray-600 text-sm">使用HSM管理密钥</p>
                    </div>
                </li>
                <li class="flex items-start bg-white p-4 rounded-lg shadow-sm border border-gray-100">
                    <i class="fas fa-chart-line text-purple-500 mt-1 mr-3"></i>
                    <div>
                        <h4 class="font-bold">行为分析引擎</h4>
                        <p class="text-gray-600 text-sm">检测异常刷新模式</p>
                    </div>
                </li>
                <li class="flex items-start bg-white p-4 rounded-lg shadow-sm border border-gray-100">
                    <i class="fas fa-random text-purple-500 mt-1 mr-3"></i>
                    <div>
                        <h4 class="font-bold">混沌工程</h4>
                        <p class="text-gray-600 text-sm">定期主动失效Token测试系统韧性</p>
                    </div>
                </li>
            </ul>
        </section>
    </div>

    <!-- Footer -->
    <footer class="bg-gray-900 text-gray-300 py-12 px-4">
        <div class="container mx-auto max-w-6xl">
            <div class="flex flex-col md:flex-row justify-between items-center">
                <div class="mb-4 md:mb-0">
                    <h3 class="text-xl font-bold text-white mb-2">技术小馆</h3>
                    <p class="text-gray-400">专业的技术分享平台</p>
                </div>
                <div>
                    <a href="http://www.yuque.com/jtostring" class="footer-link text-white hover:text-purple-400">
                        <i class="fas fa-globe mr-2"></i>
                        <span>http://www.yuque.com/jtostring</span>
                    </a>
                </div>
            </div>
        </div>
    </footer>

    <script>
        mermaid.initialize({
            startOnLoad: true,
            theme: 'default',
            flowchart: {
                useMaxWidth: false,
                htmlLabels: true,
                curve: 'basis'
            }
        });
    </script>
</body>
</html>